/**
 * 
 */
package edu.umd.clip.lm.model.training;

import java.io.*;
import java.lang.ref.*;
import java.util.*;

import com.sleepycat.util.PackedInteger;

import edu.umd.clip.lm.util.*;

/**
 * @author Denis Filimonov <den@cs.umd.edu>
 *
 */
class Word2ClustersCounts implements Serializable {
	private static final long serialVersionUID = 1L;

	private final File file;

	private transient Writing writing;
	private transient Reading reading;
	
	private static transient int nrModels = -1;
	private static transient int nrDataFiles = -1;
	
	public static void setNrModels(int nrModels) {
		Word2ClustersCounts.nrModels = nrModels;
	}

	public static void setNrDataFiles(int nrDataFiles) {
		Word2ClustersCounts.nrDataFiles = nrDataFiles;
	}

	private static class Writing {
		DetachableOutputStream output;
	}
	
	private static class Reading {
		// [fold][lmNum]
		Reference<CompactReadOnlyInt2IntHashMap[][]> cluster2count;
	}
	
	public Word2ClustersCounts(File file) {
		this.file = file;
	}
	
	public void openForWriting(Observer fileCache) {
		writing = new Writing();
		if (file.isFile()) {
			// truncate the file
			try {
				new FileOutputStream(file).close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		writing.output = new DetachableOutputStream(file, 8092, 4096);		
		writing.output.addFileOpenListener(fileCache);
	}
	
	public void addClusterCount(int fold, int lmNum, int cluster, int count) {
		int maxSize = PackedInteger.MAX_LENGTH * 4;
		byte buf[] = new byte[maxSize];
		int position = 0;

		position = PackedInteger.writeInt(buf, position, fold);
		position = PackedInteger.writeInt(buf, position, lmNum);
		position = PackedInteger.writeInt(buf, position, cluster);
		position = PackedInteger.writeInt(buf, position, count);
		
		try {
			if (writing == null || writing.output == null) {
				System.err.print("");
			}
			writing.output.write(buf, 0, position);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void finishWriting() {
		if (writing != null) {
			try {
				writing.output.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			writing = null;
		}
	}
	
	private CompactReadOnlyInt2IntHashMap[][] read() throws IOException {
		RandomAccessFile raf = new RandomAccessFile(file, "r");
		
		byte buf[] = new byte[(int) raf.length()];
		raf.readFully(buf);
		raf.close();
		
		CompactReadOnlyInt2IntHashMap[][] theHashCounts = new CompactReadOnlyInt2IntHashMap[nrDataFiles][];
		GrowingIntArray theClusters[][] = new GrowingIntArray[nrDataFiles][];
		GrowingIntArray theCounts[][] = new GrowingIntArray[nrDataFiles][];
		
		for(int fold=0; fold<nrDataFiles; ++fold) {
			theHashCounts[fold] = new CompactReadOnlyInt2IntHashMap[nrModels];
			theClusters[fold] = new GrowingIntArray[nrModels];
			theCounts[fold] = new GrowingIntArray[nrModels];
			
			for(int lmNum=0; lmNum<nrModels; ++lmNum) {
				theClusters[fold][lmNum] = new GrowingIntArray(5);
				theCounts[fold][lmNum] = new GrowingIntArray(5);
			}
		}
		
		int position = 0;
		
		while(position < buf.length) {
			int fold = PackedInteger.readInt(buf, position);
			int len = PackedInteger.getReadIntLength(buf, position);
			position += len;
			
			int lmNum = PackedInteger.readInt(buf, position);
			len = PackedInteger.getReadIntLength(buf, position);
			position += len;
			
			int cluster = PackedInteger.readInt(buf, position);
			len = PackedInteger.getReadIntLength(buf, position);
			position += len;
			
			int count = PackedInteger.readInt(buf, position);
			len = PackedInteger.getReadIntLength(buf, position);
			position += len;
			
			theClusters[fold][lmNum].add(cluster);
			theCounts[fold][lmNum].add(count);
		}
	
		buf = null;
		
		for(int fold=0; fold<nrDataFiles; ++fold) {
			for(int lmNum=0; lmNum<nrModels; ++lmNum) {
				theHashCounts[fold][lmNum] = new CompactReadOnlyInt2IntHashMap(theClusters[fold][lmNum].array(), theCounts[fold][lmNum].array());
				theClusters[fold][lmNum] = null;
				theCounts[fold][lmNum] = null;
			}
		}

		return theHashCounts;
	}
	
	public CompactReadOnlyInt2IntHashMap[][] getClusterCounts() {
		if (reading == null) {
			synchronized(this) {
				if (reading == null) {
					Reading reading = new Reading();
					reading.cluster2count = new WeakReference<CompactReadOnlyInt2IntHashMap[][]>(null);
					this.reading = reading;
				}
			}
		}
		CompactReadOnlyInt2IntHashMap[][] counts = reading.cluster2count.get();
		if (counts == null) {
			synchronized(this) {
				counts = reading.cluster2count.get();
				if (counts == null) {
					try {
						counts = read();
						reading.cluster2count = new WeakReference<CompactReadOnlyInt2IntHashMap[][]>(counts);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
		return counts;
	}
	
	public void clear() {
		file.delete();
	}
}
